package com.zty.user.websocket;

import com.zty.websocket.config.WebSocketConfig;
import com.zty.websocket.config.WebSocketServerConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author ZTY
 * @Description websocket使用demo
 * 将一个Java类标记为WebSocket端点，使其能够处理WebSocket客户端的连接和消息。
 * 在这个Java类中，我们可以实现一些WebSocket相关的方法，比如onOpen()、onMessage()、onClose()等，
 * 来处理WebSocket客户端的连接、消息和关闭事件。
 * @Date 14:28 2023-12-15
 **/

@Slf4j
@Component
/**
 * @ServerEndpoint：表示这个Java类是一个WebSocket端点。
 * value = "/zty/socket"：表示WebSocket端点的URL路径，客户端可以通过这个路径连接到WebSocket服务器端。
 * configurator = WebSocketServerConfig.class：表示WebSocket端点的配置类，用于配置WebSocket连接的参数和属性。
 */
@ServerEndpoint(value = "/zty/socket", configurator = WebSocketServerConfig.class)
public class ZTYWebsocket {

    //  静态原子化当前在线连接数
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    //  记录连接清单（存放所有在线的客户端）
    //  线程安全的HashMap
    private static Map<String, ZTYWebsocket> clients = new ConcurrentHashMap<>();

    //  某一个客户端连接上后，我要知道其session会话是什么
    private Session session;

    //  标识当前会话的key
    private String erp = "";

    /**
     * 建立连接后要做的动作
     *
     * @param session
     * @param config
     */
    @OnOpen
    public void onOpen(Session session, EndpointConfig config) throws Exception {

        //  获取用户的erp属性
        Map<String, Object> userProperties = config.getUserProperties();
        String erp = (String) userProperties.get("erp");

        //  将用户的会话和erp赋给当前连接
        this.session = session;
        this.erp = erp;

        //  判断用户是否在别的设备上登录在线了
        if (clients.containsKey(this.erp)) {

            //  如果已登录，将旧的会话下线
            clients.get(this.erp).session.close();
            clients.remove(this.erp);

            //  将在线人数减少
            onlineCount.decrementAndGet();
        }

        clients.put(this.erp, this);
        onlineCount.incrementAndGet();
        log.info("有新连接{}加入，当前在线人数为{}", erp, onlineCount.get());
        sendMessage("连接成功！", session);
    }

    /**
     * 连接关闭后执行的动作
     */
    @OnClose
    public void onClose() {
        try {
            if (clients.containsKey(erp)) {
                clients.get(erp).session.close();
                clients.remove(erp);
                onlineCount.decrementAndGet();
            }
            log.info("有一处连接关闭：{}，当前在线人数为：{}", this.erp, onlineCount.get());
        } catch (Exception e) {
            log.error("连接关闭错误，错误原因{}", e.getMessage(), e);
        }

    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message, Session session) throws Exception {
        log.info("服务端收到客户端[{}]的消息:{}", this.erp, message);
        //心跳机制，要跟前端约定好，要定期发送一些东西，我接收到后返回消息给你
        if (message.equals("ping")) {
            this.sendMessage("pong", session);
        }
    }

    /**
     * 连接发生错误所做的动作
     *
     * @param session
     * @param throwable
     */
    @OnError
    public void onError(Session session, Throwable throwable) throws IOException {
        log.error("websocket:{}，发生错误，错误原因：{}", erp, throwable.getMessage(), throwable);
        session.close();
    }

    /**
     * 连接成功后要往客户端发送消息
     *
     * @param message
     * @param session
     */
    public void sendMessage(String message, Session session) throws Exception {
        log.info("服务端给客户端{}发送消息{}", this.erp, message);
        session.getBasicRemote().sendText(message);
    }

    /**
     * 将消息发送至每一个客户端
     *
     * @param message
     */
    public void sendMessage(String message) {

        //  遍历客户端
        for (Map.Entry<String, ZTYWebsocket> sessionEntry : clients.entrySet()) {
            String erp = sessionEntry.getKey();
            ZTYWebsocket socket = sessionEntry.getValue();
            Session session = socket.session;
            log.info("服务端给客户端[{}]发送消息{}", erp, message);
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("{}发送消息发生异常，异常原因{}", this.erp, message);
            }
        }

    }
}